﻿using XAMSDK.Types;
using System.Collections.Generic;
using System;

namespace XAMSDK
{
    /// <summary>
    /// The Xset is the fundamental container of a XAM Strorage platform. Consists of fields of 2 types - Properties (which have
    /// primitive SNIA-defined types and contain the metadata) and XStreams (which contain the raw binary content).
    /// </summary>
    public class XSet : XAMObject
    {
        /// <summary>
        /// A Retention Policy is a conceptual grouping of properties that logically constitute the policy.
        /// <para></para>
        /// <para>Name - the ID / name of the RetentionPolicy e.g. "event".</para>
        /// <para>Duration - the length (in millisconds) of the retention, or the value associated with the DurationPolicy.</para>
        /// <para>StartTime - the time that the RetentionPolicy "clock" started ticking</para>
        /// <para>Enabled - is the PolicyType enabled?</para>
        /// </summary>
        public class RetentionPolicy
        {
            XSetHandle handle;
            internal XString schemeName = new XString();
            internal bool enabled = false;
            internal DateTime start = new DateTime();
            internal long duration = 0;

            #region Constructors
            /// <summary>
            /// Internal constructor that is called by the factory methods of the XSet
            /// </summary>
            /// <param name="xs">the XSet that the policy applies to</param>
            /// <param name="n">the name of the RetentionPolicy e.g. "event"</param>
            internal RetentionPolicy(XSet xs, XString n)
            {
                handle = xs.handle;
                schemeName = n;

                try
                {
                    enabled = Enabled;
                    duration = Duration;
                    start = StartTime;
                }
                catch (XAMException e)
                {
                    if (e.Status != Errors.FIELD_NOT_FOUND)
                        throw (e);
                }
            }
            #endregion

            #region Properties

            /// <summary>
            /// The ID of this RetentionPolicy scheme e.g. "event"
            /// </summary>
            public XString RetentionID { get { return schemeName; } }

            /// <summary>
            /// The Enabled state of the RetentionPolicy
            /// </summary>
            public virtual bool Enabled
            {
                get
                {
                    bool enabled = false;

                    int retval = Imports.XSet_GetActualRetentionEnabled(handle, schemeName, out enabled);
                    Helpers.CheckAndThrow(retval, "XSet_GetActualRetentionEnabled");

                    return enabled;
                }

                set { SetEnabled(value, true); }
            }

            /// <summary>
            /// The StartTime of the RetentionPolicy. If not set yet, maximum value for DateTime will be returned.
            /// </summary>
            public DateTime StartTime
            {
                get
                {
                    DateTime startTime = new DateTime(9999, 12, 31);
                    bool containsField = false;
                    XString fieldName = ".xset.retention." + schemeName + ".starttime";
                    XString start = new XString();

                    int retval = Imports.XAM_ContainsField((XAMHandle) handle, fieldName, out containsField);
                    Helpers.CheckAndThrow(retval, "XAM_ContainsField");

                    if (containsField)
                    {
                        retval = Imports.XAM_GetDatetime((XAMHandle)handle, fieldName, start);
                        Helpers.CheckAndThrow(retval, "XSet_SetRetentionStartTime");
                        startTime = DateTime.Parse(start);
                    }

                    return startTime;
                }
            }

            /// <summary>
            /// The Duration of the RetentionPolicy
            /// </summary>
            public virtual long Duration
            {
                get
                {
                    long duration = 0;
                    int retval = Imports.XSet_GetActualRetentionDuration(handle, schemeName, out duration);
                    Helpers.CheckAndThrow(retval, "XSet_GetActualRetentionDuration");

                    return duration;
                }

                set { SetDuration(value, true); }
            }

            /// <summary>
            /// The ExpiryTime of the RetentionPolicy
            /// </summary>
            public DateTime Expiry
            {
                get
                {
                    if (duration == -1)
                        return new DateTime(9999, 12, 31);
                    else
                        return start.AddMilliseconds(Duration);
                }
            }
            #endregion

            #region Methods
            /// <summary>
            /// Creates a string representation of the RetentionPolicy using its Properties.
            /// </summary>
            /// <returns>the string representation</returns>
            public override string ToString()
            {
                string value = "Name " + schemeName + " Enabled " + Enabled + " Duration " + Duration;

                if (Enabled)
                {
                    value += " Start " + StartTime + " Expiry " + Expiry;
                }

                return value;
            }

            /// <summary>
            /// Sets the Enabled state of the RetentionPolicy
            /// </summary>
            /// <param name="enabled">the state to be set.</param>
            /// <param name="binding">the binding status of the Enabled field</param>
            public void SetEnabled(bool enabled, bool binding)
            {
                int retval = Imports.XSet_SetRetentionEnabledFlag(handle, schemeName, binding, enabled);
                Helpers.CheckAndThrow(retval, "XSet_SetRetentionEnabledFlag");
            }

            /// <summary>
            /// Sets the Duration of the RetentionPolicy
            /// </summary>
            /// <param name="duration">the duration of the RetentionPolicy (in milliseconds)</param>
            /// <param name="binding">the binding status of the  Duration field</param>
            public void SetDuration(long duration, bool binding)
            {
                int retval = Imports.XSet_SetRetentionDuration(handle, schemeName, binding, duration);
                Helpers.CheckAndThrow(retval, "XSet_SetRetentionDuration");
            }

            /// <summary>
            /// Starts the RetentionPeriod clock "ticking" from this point in time
            /// </summary>
            /// <param name="binding">the binding status of the Starttime field</param>
            public void Start(bool binding)
            {
                int retval = Imports.XSet_SetRetentionStarttime(handle, schemeName, binding);
                Helpers.CheckAndThrow(retval, "XSet_SetRetentionStartTime");
            }

            /// <summary>
            /// Convenience methods which Starts the RetentionPeriod clock "ticking" from this point in time
            /// using a binding status of True for the affected field.
            /// </summary>
            public void Start() { Start(true); }

            /// <summary>
            /// Applies a predefined RetentionDuration or RetentionEnabled policy to this instance
            /// </summary>
            /// <param name="policy">the XPolicyType to be applied</param>
            /// <param name="inPolicyName">the name of the predefined scheme</param>
            /// <param name="inBinding">the binding status of the Enabled or Duration field</param>
            public void ApplyPolicy(XPolicyType policy, string inPolicyName, bool inBinding)
            {
                int retval = Errors.INVALID_PARAM;
                XString msg = new XString();

                switch (policy)
                {
                    case XPolicyType.RetentionEnabled:
                        retval = Imports.XSet_ApplyRetentionEnabledPolicy(handle, schemeName, inBinding, inPolicyName);
                        msg = "XSet_ApplyRetentionEnabledPolicy";
                        break;

                    case XPolicyType.RetentionDuration:
                        retval = Imports.XSet_ApplyRetentionDurationPolicy(handle, schemeName, inBinding, inPolicyName);
                        msg = "XSet_ApplyRetentionDurationPolicy";
                        break;
                }

                Helpers.CheckAndThrow(retval, msg);
            }

            /// <summary>
            /// Convenience methods which applies a predefined RetentionDuration or RetentionEnabled policy to this instance
            /// using a binding status of True for the affected field.
            /// </summary>
            /// <param name="policy">the XPolicyType to be applied</param>
            /// <param name="inPolicyName">the name of the predefined scheme</param>
            public void ApplyPolicy(XPolicyType inType, string inPolicyName) { ApplyPolicy(inType, inPolicyName, true); }
            #endregion
        }
        
        static private List<XSet> xsetObjects = new List<XSet>();
        internal XSetHandle handle;
        internal XSystem xsys;
        private XUID xuid = new XUID();

        #region Constructors
        /// <summary>
        /// Internal constructor called by the factory methods of an XSystem
        /// </summary>
        /// <param name="h">the XSetHandle associated with the new instance</param>
        internal XSet(XSetHandle h)
        {
            handle = h;
            xamHandle = (XAMHandle)handle;
            lock (xsetObjects)
                xsetObjects.Add(this);
        }
        #endregion

        #region Factory Methods
        /// <summary>
        /// Creates a new RetentionPolicy object with the supplied ID on this XSet instance.
        /// </summary>
        /// <param name="schemeName">the ID / name of the RetentionPolicy e.g. "event"</param>
        /// <param name="binding">the binding status of RetentionPolicy name</param>
        /// <returns></returns>
        public RetentionPolicy CreateRetention(XString schemeName, bool binding)
        {
            int retval = Imports.XSet_CreateRetention(handle, binding, schemeName);
            Helpers.CheckAndThrow(retval, "XSet_CreateRetention");

            return new RetentionPolicy(this, schemeName);
        }

        /// <summary>
        /// Conveniemnce method which creates a new RetentionPolicy object with the supplied ID on this XSet instance
        /// with the ID / name being a binding field.
        /// </summary>
        /// <param name="schemeName">the ID / name of the RetentionPolicy e.g. "event"</param>
        /// <returns></returns>
        public RetentionPolicy CreateRetention(XString schemeName) { return CreateRetention(schemeName, true); }

        /// <summary>
        /// Constructs a RetentionPolicy object from field values of a scheme already present on this XSet instance
        /// </summary>
        /// <param name="schemeName"></param>
        /// <returns></returns>
        public RetentionPolicy GetRetention(XString schemeName)
        {
            return new RetentionPolicy(this, schemeName);
        }
        #endregion

        #region Properties
        /// <summary>
        /// The XSetHandle associated with this instance.
        /// </summary>
        internal XSetHandle Handle
        {
            get
            {
                return handle;
            }
        }

        /// <summary>
        /// The AutoDelete policy for this XSet upon Retention Expiry.
        /// Note that the XSystem does not have to honour this if it does not support AutoDelete.
        /// Using "set" on this property will create the field as binding (use SetAutoDelete if you wish to specify the binding status).
        /// </summary>
        public bool AutoDelete
        {
            get
            {
                bool auto = false;
                int retval = Imports.XSet_GetActualAutoDelete(handle, out auto);
                Helpers.CheckAndThrow(retval, "XSet_GetActualAutoDelete");
                return auto;
            }

            set
            {
                SetAutoDelete(value, true);
            }
        }

        /// <summary>
        /// The Shred policy for this XSet upon Delete.
        /// Note that the XSystem does not have to honour this if it does not support Shredding.
        /// Using "set" on this property will create the field as binding (use SetShred if you wish to specify the binding status).
        /// </summary>
        public bool Shred
        {
            get
            {
                bool auto = false;
                int retval = Imports.XSet_GetActualShred(handle, out auto);
                Helpers.CheckAndThrow(retval, "XSet_GetActualShred");
                return auto;
            }

            set
            {
                SetShred(value, true);
            }
        }

        /// <summary>
        /// Is this XSet on hold?
        /// </summary>
        public bool OnHold
        {
            get
            {
                bool held;
                GetField(Constants.XSET_HOLD, out held);

                return held;
            }
        }

        /// <summary>
        /// The BaseRetention of the Xset (in milliseconds).
        /// Using "set" on this property will create the field as binding (use SetBaseRetention if you wish to specify the binding status).
        /// </summary>
        public long BaseRetention
        {
            get
            {
                long value = 0;
                if (ContainsField(Constants.XSET_RETENTION_BASE_DURATION))
                    GetField(Constants.XSET_RETENTION_BASE_DURATION, out value);
                return value;
            }

            set
            {
                SetBaseRetention(value, true);
            }
        }

        /// <summary>
        /// The XUID associated with this XSet
        /// </summary>
        public XUID XUID { get { XUID xuid = new XUID(); if (ContainsField(Constants.XSET_XUID)) GetField(Constants.XSET_XUID, xuid); return xuid; } }

        /// <summary>
        /// Is the XSet in memory Dirty i.e. does has it been modified and needs to be Comitted?
        /// </summary>
        public bool Dirty { get { bool dirty; GetField(Constants.XSET_DIRTY, out dirty); return dirty; } }

        /// <summary>
        /// The time that the Xset was created
        /// </summary>
        public DateTime CreationTime { get { DateTime t = new DateTime(); GetField(Constants.XSET_TIME_CREATION, out t); return t; } }

        /// <summary>
        /// The time that the XSet was Comiited
        /// </summary>
        public DateTime CommitTime { get { DateTime t = new DateTime(); GetField(Constants.XSET_TIME_COMMIT, out t); return t; } }

        /// <summary>
        /// The last time the XSet was accessed
        /// </summary>
        public DateTime AccessTime { get { DateTime t = new DateTime(); GetField(Constants.XSET_TIME_ACCESS, out t); return t; } }

        /// <summary>
        /// The time that the XSet arrived on the XSystem i.e. how long it has been resident on the XSystem
        /// </summary>
        public DateTime ResidencyTime { get { DateTime t = new DateTime(); GetField(Constants.XSET_TIME_RESIDENCY, out t); return t; } }
        #endregion

        #region Methods
        /// <summary>
        /// Finds the XSet object associated with a supplied XSetHandle
        /// </summary>
        /// <param name="h"></param>
        /// <returns></returns>
        internal static XSet Get(XSetHandle h)
        {
            XSet xs;

            lock (xsetObjects)
                xs = xsetObjects.Find(delegate(XSet s) { return s.handle == h; });

            if (xs == null)
            {
                xs = new XSet(h);
            }

            return xs;
        }

        /// <summary>
        /// Explicitly close the XSet - this will be called by the base Dispose methods.
        /// </summary>
        protected override void Close()
        {
            lock (xsetObjects)
            {
                if (!disposed)
                {
                    int retval = Imports.XSet_Close(handle);
                    Helpers.CheckAndThrow(retval, "XSet_Close");
                }
                ((XAMObject)xsys).RemoveFactoryObject(this);
                xsetObjects.Remove(this);
            }
        }

        /// <summary>
        /// Sets the standard BaseRetention period for this instance
        /// </summary>
        /// <param name="duration">the duration (in milliseconds) of the Base retention</param>
        /// <param name="binding">the binding status of the Base retention duration field</param>
        public void SetBaseRetention(long duration, bool binding)
        {
            int retval = Imports.XSet_SetBaseRetention(handle, binding, duration);
            Helpers.CheckAndThrow(retval, "XSet_SetBaseRetention");
        }

        /// <summary>
        /// Sets the AutoDelete policy associated for this instance.
        /// Note: the XSystem does not have to honour this (i.e. carry out an AutoDelete on Retention Expiry) if it
        /// does not support AutoDelete. In other words, this is merely a request to delete it if the XSystem supports AutoDelete.
        /// </summary>
        /// <param name="auto">should the XSystem delete this XSet when retentionm expires?</param>
        /// <param name="binding">the binding status of the AutoDelete field</param>
        public void SetAutoDelete(bool auto, bool binding)
        {
            int retval = Imports.XSet_SetAutoDelete(handle, binding, auto);
            Helpers.CheckAndThrow(retval, "XSet_SetAutoDelete");
        }

        /// <summary>
        /// Sets the Shred policy associated for this instance.
        /// Note: the XSystem does not have to honour this (i.e. carry out a Shred on Delete) if it
        /// does not support Shredding. In other words, this is merely a request to shred it if the XSystem supports Shredding.
        /// </summary>
        /// <param name="auto">should the XSystem delete this XSet when retentionm expires?</param>
        /// <param name="binding">the binding status of the AutoDelete field</param>
        public void SetShred(bool shred, bool binding)
        {
            int retval = Imports.XSet_SetShred(handle, binding, shred);
            Helpers.CheckAndThrow(retval, "XSet_SetShred");
        }

        /// <summary>
        /// Commits the XSet to the XSystem
        /// </summary>
        /// <returns>the XUID of the newly comitted XSet</returns>
        public XUID Commit()
        {
            int retval = Imports.XSet_Commit(handle, xuid);
            Helpers.CheckAndThrow(retval, "XSet_Commit");
            return xuid;
        }

        /// <summary>
        /// Asynchronously commits the XSet.
        /// </summary>
        /// <param name="opID">a user supplied ID for this asynchronous operation</param>
        /// <param name="callback">the callback routine when the operation completes</param>
        /// <returns>an XAsyncObject representing this operation</returns>
        public XAsyncObject Commit(long opID, XAsyncCallback callback)
        {
            XAsyncHandle xah;

            int retval = Imports.XSet_AsyncCommit(handle, opID, callback, out xah);
            Helpers.CheckAndThrow(retval, "XSet_Commit");

            return new XAsyncObject(xah, XAsyncType.XSetCommit);
        }

        /// <summary>
        /// Abadndon the curretn XSet - should only be used if proper / graceful closing is not possible due to errors.
        /// </summary>
        public void Abandon()
        {
            int retval = Imports.XSet_Abandon(handle);
            Helpers.CheckAndThrow(retval, "XSet_Abandon");
        }

        /// <summary>
        /// Opens an XStream which contains a serialized representation of the XSet.
        /// This XStream can then be used to create an external representation of the XSet.
        /// </summary>
        /// <returns>the XStream that the XSet will be read from</returns>
        public XStream OpenExportXStream()
        {
            XStreamHandle streamh = 0;

            int retval = Imports.XSet_OpenExportXStream(handle, out streamh);
            Helpers.CheckAndThrow(retval, "XSet_OpenExportXStream");
            return new XStream(streamh, Constants.OUTPUT_STREAM);
        }

        /// <summary>
        /// Opens an import XStream that an external serialized representation of the XSet can be written into.
        /// </summary>
        /// <returns>the XStream that the XSet will be written into.</returns>
        public XStream OpenImportXStream()
        {
            XStreamHandle streamh = 0;

            int retval = Imports.XSet_OpenImportXStream(handle, out streamh);
            Helpers.CheckAndThrow(retval, "XSet_OpenImportXStream");

            return new XStream(streamh, Constants.INPUT_STREAM);
        }

        /// <summary>
        /// Removes any Access related fields associated with this XSet
        /// </summary>
        public void ResetAccessFields()
        {
            int retval = Imports.XSet_ResetAccessFields(handle);
            Helpers.CheckAndThrow(retval, "XSet_ResetAccessFields");
        }

        /// <summary>
        /// Removes any Management fields associated with this XSet. This will typically result in a new XUID on Commit.
        /// </summary>
        public void ResetManagementFields()
        {
            int retval = Imports.XSet_ResetManagementFields(handle);
            Helpers.CheckAndThrow(retval, "XSet_ResetManagementFields");
        }

        /// <summary>
        /// Applies a predefined Policy to an existing RetentionPolicy of this XSet
        /// </summary>
        /// <param name="inRetentionID">the name / ID of the existing policy of this XSet instance</param>
        /// <param name="inType">the XPolicyType that will be applied</param>
        /// <param name="inPolicyName">the predefined Policy to be applied to the existing RetentionPolicy of the XSet</param>
        /// <param name="inBinding">the binding status of the resulting Policy Duration / Enabled field</param>
        public void ApplyPolicy(string inRetentionID, XPolicyType inType, string inPolicyName, bool inBinding)
        {
            int retval = Errors.INVALID_PARAM;

            switch (inType)
            {
                case XPolicyType.RetentionDuration:
                    retval = Imports.XSet_ApplyRetentionDurationPolicy(handle, inRetentionID, inBinding, inPolicyName);
                    break;
                case XPolicyType.RetentionEnabled:
                    retval = Imports.XSet_ApplyRetentionDurationPolicy(handle, inRetentionID, inBinding, inPolicyName);
                    break;
            }

            Helpers.CheckAndThrow(retval, "XSet_ApplyRetentionPolicyType");
        }

        /// <summary>
        /// Convenience method which applies a predefined Policy to an existing RetentionPolicy of this XSet
        /// with the resulting Policy Duration / Enabled field being binding 
        /// </summary>
        /// <param name="inRetentionID">the name / ID of the existing policy of this XSet instance</param>
        /// <param name="inType">the XPolicyType that will be applied</param>
        /// <param name="inPolicyName">the predefined Policy to be applied to the existing RetentionPolicy of the XSet</param>
        public void ApplyPolicy(string inRetentionID, XPolicyType inType, string inPolicyName) { ApplyPolicy(inRetentionID, inType, inPolicyName, true); }

        /// <summary>
        /// Applies a Policy to this XSet instance
        /// </summary>
        /// <param name="inType">the XPolicyType of the Policy being applied</param>
        /// <param name="inPolicyName">the name of the Policy being applied</param>
        /// <param name="inBinding">the binding status of the resulting Policy field</param>
        public void ApplyPolicy(XPolicyType inType, string inPolicyName, bool inBinding)
        {
            int retval = Errors.INVALID_PARAM;

            switch (inType)
            {
                case XPolicyType.Access:
                    retval = Imports.XSet_ApplyAccessPolicy(handle, inBinding, inPolicyName);
                    break;
                case XPolicyType.Management:
                    retval = Imports.XSet_ApplyManagementPolicy(handle, inBinding, inPolicyName);
                    break;
                case XPolicyType.BaseRetention:
                    retval = Imports.XSet_ApplyBaseRetentionPolicy(handle, inBinding, inPolicyName);
                    break;
                case XPolicyType.AutoDelete:
                    retval = Imports.XSet_ApplyAutoDeletePolicy(handle, inBinding, inPolicyName);
                    break;
                case XPolicyType.Shred:
                    retval = Imports.XSet_ApplyShredPolicy(handle, inBinding, inPolicyName);
                    break;
                case XPolicyType.Storage:
                    retval = Imports.XSet_ApplyStoragePolicy(handle, inBinding, inPolicyName);
                    break;
            }

            Helpers.CheckAndThrow(retval, "XSet_ApplyPolicyType");
        }

        /// <summary>
        /// Convenience method which applies a Policy to this XSet instance with the resulting field being binding
        /// </summary>
        /// <param name="inType">the XPolicyType of the Policy being applied</param>
        /// <param name="inPolicyName">the name of the Policy being applied</param>
        public void ApplyPolicy(XPolicyType inType, string inPolicyName) { ApplyPolicy(inType, inPolicyName, true); }

        /// <summary>
        /// Gets the value of a Policy that has been applied to this XSet
        /// </summary>
        /// <param name="inType">the XPolicyType to be examined</param>
        /// <returns>the value of the property (XString or bool)</returns>
        public object GetPolicy(XPolicyType inType)
        {
            XString msg = new XString("GetPolicy - Field ");
            XString strPolicy = new XString();
            bool boolPolicy = false;
            object policy = null;

            switch (inType)
            {
                case XPolicyType.Access:
                    if (ContainsField(Constants.XSET_ACCESS_POLICY))
                    {
                        GetField(Constants.XSET_ACCESS_POLICY, strPolicy);
                        policy = strPolicy;
                    }
                    break;

                case XPolicyType.Management:
                    GetField(Constants.XSET_MANAGEMENT_POLICY, strPolicy);
                    policy = strPolicy;
                    break;

                case XPolicyType.AutoDelete:
                    GetField(Constants.XSET_DELETION_AUTODELETE_POLICY, out boolPolicy);
                    policy = boolPolicy;
                    break;

                case XPolicyType.Shred:
                    GetField(Constants.XSET_DELETION_SHRED_POLICY, strPolicy);
                    policy = strPolicy;
                    break;

                case XPolicyType.Storage:
                    GetField(Constants.XSET_STORAGE_POLICY, strPolicy);
                    policy = strPolicy;
                    break;

                case XPolicyType.RetentionDuration:
                    GetField(Constants.XSET_RETENTION_BASE_DURATION_POLICY, strPolicy);
                    policy = strPolicy;
                    break;

                case XPolicyType.RetentionEnabled:
                    if (ContainsField(Constants.XSET_RETENTION_BASE_ENABLED_POLICY))
                    {
                        GetField(Constants.XSET_RETENTION_BASE_ENABLED_POLICY, strPolicy);
                        policy = strPolicy;
                    }
                    break;
            }

            return policy;
        }
        #endregion
    }

}